//
// Created by antler on 2018/12/7.
//

#include <string>

#include "lua.hpp"
#include "luaext.hpp"

#include "log.h"
#include "GlobalMacros.h"

#include "Rect.h"

static int rect_constructor(lua_State *L) {
    Rect *ret = nullptr;

    int argc = lua_gettop(L) - 1;
    if (argc == 0) {
        ret = Rect::create();
    } else if (argc == 1) {
        Rect *obj = (Rect *) toluahelper_tousertype(L, 2, USERTYPE_RECT, nullptr);
        if (obj) {
            ret = Rect::createWith(*obj);
        }
    } else if (argc == 2) {
        Point *point = (Point *) (toluahelper_tousertype(L, 2, USERTYPE_POINT, nullptr));
        Size *size = (Size *) (toluahelper_tousertype(L, 3, USERTYPE_SIZE, nullptr));
        if (point && size) {
            ret = Rect::createWith(*point, *size);
        }
    } else if (argc == 4) {
        int x = luaL_checkint(L, 2);
        int y = luaL_checkint(L, 3);
        int width = luaL_checkint(L, 4);
        int height = luaL_checkint(L, 5);
        ret = Rect::createWith(x, y, width, height);
    }

    if (ret) {
        LOGV("rect:constructor self:%p", ret);
        tolua_pushusertype_and_takeownership(L, ret, USERTYPE_RECT);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int rect_destructor(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, 0);
    LOGV("rect:destructor self:%p", self);
    SAFE_DELETE(self);
    return 0;
}

static int rect_tostring(lua_State *L) {
    Rect *self = (Rect *) toluahelper_tousertype(L, 1, USERTYPE_RECT, nullptr);
    if (self) {
        lua_pushfstring(L, "%s<Point(%d, %d), Size[%d x %d]>", tolua_typename(L, 1),
                        self->x,
                        self->y,
                        self->width,
                        self->height);
    } else {
        lua_pushfstring(L, "class: %p", lua_topointer(L, 1));
    }
    return 1;
}

static int rect_tl(lua_State *L) {
    Rect *self = (Rect *) toluahelper_tousertype(L, 1, USERTYPE_RECT, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in function 'tl'", nullptr);
    }

    Point *tl = Point::createWith(self->tl());
    tolua_pushusertype_and_takeownership(L, tl, USERTYPE_POINT);
    return 1;
}

static int rect_br(lua_State *L) {
    Rect *self = (Rect *) toluahelper_tousertype(L, 1, USERTYPE_RECT, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in function 'br'", nullptr);
    }

    Point *br = Point::createWith(self->br());
    tolua_pushusertype_and_takeownership(L, br, USERTYPE_POINT);
    return 1;
}

static int rect_contains(lua_State *L) {
    Rect *self = (Rect *) toluahelper_tousertype(L, 1, USERTYPE_RECT, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in function 'contains'", nullptr);
    }

    tolua_Error tolua_err;
    Point *point = (Point *) toluahelper_tousertype(L, 2, USERTYPE_POINT, nullptr, &tolua_err);
    if (point) {
        lua_pushboolean(L, self->contains(*point) ? 1 : 0);
    } else {
        tolua_error(L, "#frect:contains", &tolua_err);
    }
    return 1;
}

static int rect_union(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in function 'union'", nullptr);
    }

    tolua_Error tolua_err;
    Rect *rect = (Rect *) toluahelper_tousertype(L, 2, USERTYPE_RECT, nullptr, &tolua_err);
    if (rect) {
        Rect *unionRect = Rect::createWith(self->unionRect(*rect));
        tolua_pushusertype_and_takeownership(L, unionRect, USERTYPE_RECT);
    } else {
        tolua_error(L, "#frect:union", &tolua_err);
    }
    return 1;
}

static int rect_intersect(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in function 'intersect'", nullptr);
    }

    tolua_Error tolua_err;
    Rect *rect = (Rect *) toluahelper_tousertype(L, 2, USERTYPE_RECT, nullptr, &tolua_err);
    if (rect) {
        Rect *intersectRect = Rect::createWith(self->intersectRect(*rect));
        tolua_pushusertype_and_takeownership(L, intersectRect, USERTYPE_RECT);
    } else {
        tolua_error(L, "#frect:intersect", &tolua_err);
    }
    return 1;
}

static const luaL_Reg funcs[] = {
        {".call",      rect_constructor},
        {"__tostring", rect_tostring},
        {"tl",         rect_tl},
        {"br",         rect_br},
        {"contains",   rect_contains},
        {"union",      rect_union},
        {"intersect",  rect_intersect},
        {nullptr,      nullptr}
};

static int get_ZERO(lua_State *L) {
    tolua_pushusertype(L, &Rect::ZERO, USERTYPE_RECT);
    return 1;
}

static int get_x(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'x'", nullptr);
    }
    lua_pushinteger(L, self->x);
    return 1;
}

static int set_x(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'x'", nullptr);
    }
    self->x = luaL_checkinteger(L, 2);
    return 0;
}

static int get_y(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'y'", nullptr);
    }
    lua_pushinteger(L, self->y);
    return 1;
}

static int set_y(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'y'", nullptr);
    }
    self->y = luaL_checkinteger(L, 2);
    return 0;
}

static int get_width(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'width'", nullptr);
    }
    lua_pushinteger(L, self->width);
    return 1;
}

static int set_width(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'width'", nullptr);
    }
    self->width = luaL_checkinteger(L, 2);
    return 0;
}

static int get_height(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'height'", nullptr);
    }
    lua_pushinteger(L, self->height);
    return 1;
}

static int set_height(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'height'", nullptr);
    }
    self->height = luaL_checkinteger(L, 2);
    return 0;
}

static int get_size(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'size'", nullptr);
    }
    Size *size = Size::createWith(self->width, self->height);
    tolua_pushusertype_and_takeownership(L, size, USERTYPE_SIZE);
    return 1;
}

static int set_size(lua_State *L) {
    Rect *self = (Rect *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'size'", nullptr);
    }

    tolua_Error tolua_err;
    Size *size = (Size *) toluahelper_tousertype(L, 2, USERTYPE_SIZE, nullptr, &tolua_err);
    if (size) {
        self->width = size->width;
        self->height = size->height;
    } else {
        tolua_error(L, "#frect:size", &tolua_err);
    }
    return 0;
}

LUAMOD_API int luaopen_rect(lua_State *L) {
    tolua_open(L);

    tolua_module(L, nullptr, 0);
    tolua_beginmodule(L, nullptr);
    {
        tolua_usertype(L, USERTYPE_RECT);
        tolua_cclass(L, CLASSNAME_RECT, USERTYPE_RECT, "", rect_destructor);

        tolua_beginmodule(L, CLASSNAME_RECT);
        {
            toluahelper_setfunc(L, funcs);

            TOLUAHELPER_CLASS_CONST_VARIABLE(L, ZERO);

            TOLUAHELPER_CLASS_VARIABLE(L, x);
            TOLUAHELPER_CLASS_VARIABLE(L, y);
            TOLUAHELPER_CLASS_VARIABLE(L, width);
            TOLUAHELPER_CLASS_VARIABLE(L, height);
            TOLUAHELPER_CLASS_VARIABLE(L, size);
        }
        tolua_endmodule(L);
    }
    tolua_endmodule(L);
    return 0;
}